home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 August: Tool Chest / Dev.CD Aug 98 TC.toast / Sample Code / Processes / ProcDoggie2.1b2 / UProcessUtils.p < prev   
Encoding:
Text File  |  1997-11-03  |  45.1 KB  |  1,204 lines  |  [TEXT/CWIE]

  1. UNIT UProcessUtils;
  2.  
  3. {-------------------------------------------------------------------------------
  4.     File:        UProcessUtils.p
  5.  
  6.     Contains:    Guts of the ProcDoggie application.
  7.  
  8.     Written by:    Forrest Tanaka
  9.  
  10.     Copyright:    © 1988-1997 by Apple Computer, Inc., all rights reserved.
  11.  
  12.     Change History (most recent first):
  13.  
  14.     You may incorporate this sample code into your applications without
  15.     restriction, though the sample code has been provided "AS IS" and the
  16.     responsibility for its operation is 100% yours.  However, what you are
  17.     not permitted to do is to redistribute the source as "DSC Sample Code"
  18.     after having made changes. If you're going to re-distribute the source,
  19.     we require that you make it clear in the source that the code was
  20.     descended from Apple Sample Code, but that you've made changes.
  21. --------------------------------------------------------------------------------
  22. #
  23. #    This unit is a high-level interface to the Process Manager.  It contains
  24. #    routines which allow you to launch a process, terminate a process, count the
  25. #    number of open processes, find a process given a file specification, and
  26. #    manage a list of documents for a process to open or print when it is
  27. #    launched.
  28. #
  29. #    The LaunchProcess routine is the most important routine in this unit.  It
  30. #    lets you launch either an application or a desk accessory and optionally lets
  31. #    you pass it a list of documents for the launched application to open or print
  32. #    (desk accessories don’t have document lists).
  33. #
  34. -------------------------------------------------------------------------------}
  35. {[j=20/57/1$] Pasmat Options}
  36.  
  37.  
  38. INTERFACE
  39.  
  40.     USES
  41.         Processes;
  42.  
  43.  
  44. (*******************************************************************************
  45. * Used Units
  46. *******************************************************************************)
  47.  
  48.  
  49. (*******************************************************************************
  50. * Types
  51. *******************************************************************************)
  52.  
  53.     TYPE
  54.         (* Flag to indicate whether document list is for printing or opening *)
  55.         LaunchModeCode = (kJustLaunch, kOpenLaunch, kPrintLaunch);
  56.  
  57.         (* Document list entry for opening apps with docs to open or print *)
  58.         DocListEntryPtr = ^DocListEntryRec;
  59.         DocListEntryHnd = ^DocListEntryPtr;
  60.         DocListEntryRec = RECORD
  61.             next:    DocListEntryHnd; {Link to the next document list record}
  62.             docFile: FSSpec           {File specification}
  63.         END;
  64.  
  65.         (* Document list for opening apps with docs to open or print *)
  66.         DocListRec = RECORD
  67.             docList:   DocListEntryHnd; {Handle to the first document list entry}
  68.             openPrint: LaunchModeCode   {Opening or printing documents?}
  69.         END;
  70.         DocListPtr = ^DocListRec;
  71.         DocListHnd = ^DocListPtr;
  72.  
  73.  
  74. (*******************************************************************************
  75. * CreateDocList - Create a new document list
  76. *
  77. * This routine creates a new document list and returns a handle to it.  If there
  78. * isn’t enough memory for a new document list, CreateDocList returns NIL.
  79. *
  80. * If openOrPrint is kOpenLaunch, then the list of documents that are put into
  81. * the new document list will be opened when the application is launched.  If
  82. * openOrPrint is kPrintLaunch, then the list of documents will be printed when
  83. * the application is launched.  If kJustLaunch or any other value is passed in
  84. * openOrPrint, then no document list is created.  CreateDocList returns NIL in
  85. * this case.  This isn’t really a conflict with returning NIL when there’s not
  86. * enough memory for the new document list because the calling routine can check
  87. * to see if openOrPrint is valid or not itself.
  88. *******************************************************************************)
  89.  
  90.     FUNCTION CreateDocList (openOrPrint: LaunchModeCode): DocListHnd;
  91.  
  92.  
  93. (*******************************************************************************
  94. * IsEmptyDocList - Return TRUE if the specified document list is empty.
  95. *
  96. * IsEmptyDocList returns TRUE if the document list specified by "theList" is
  97. * empty.  “Empty” can mean either that theList is NIL or theList is a handle to
  98. * a document list that contains no documents.  It returns FALSE if there’s at
  99. * least one entry in "theList".
  100. *
  101. * theList must either be a valid handle to a document list or it must be NIL.
  102. * IsEmptyDocList is unpredictable if this isn’t true.
  103. *******************************************************************************)
  104.  
  105.     FUNCTION IsEmptyDocList (theList: DocListHnd): Boolean;
  106.  
  107.  
  108. (*******************************************************************************
  109. * AddToDocList - Add a document file specification to a document list
  110. *
  111. * AddToDocList adds the file specified by "newFile" to the end of the document
  112. * list specified by "theList".  If there isn’t enough memory to add a new file
  113. * to the document list, then AddToDocList returns memFullErr.  Otherwise, noErr
  114. * is returned.
  115. *
  116. * theList MUST be a valid handle to an initialized document list.  I won’t be
  117. * responsible for AddToDocList’s actions if this isn’t true.
  118. *******************************************************************************)
  119.  
  120.     FUNCTION AddToDocList (newFile: FSSpec;
  121.                            theList: DocListHnd): OSErr;
  122.  
  123.  
  124. (*******************************************************************************
  125. * DisposeDocList - Dispose of a document list
  126. *
  127. * DisposeDocList deallocates all the memory occupied by the document list
  128. * specified by the "theList" parameter.  If theList is NIL, then nothing is
  129. * done.  If it’s not a valid DocListHnd, DisposeDocList is unpredictable.
  130. *******************************************************************************)
  131.  
  132.     PROCEDURE DisposeDocList (theList: DocListHnd);
  133.  
  134.  
  135. (*******************************************************************************
  136. * WereInFront - Test to see if this application is in front or not
  137. *
  138. * This routine determines whether this application is the front-most application
  139. * or not.  If it is, then TRUE is returned.  If it isn’t, then FALSE is
  140. * returned.
  141. *******************************************************************************)
  142.  
  143.     FUNCTION WereInFront: Boolean;
  144.  
  145.  
  146. (*******************************************************************************
  147. * FindProcess - Find a process with specified file and name
  148. *
  149. * FindProcess searches the Process Manager’s process list for a process that was
  150. * launched from the file specified by "testFile".  If the process being searched
  151. * is a DA, then the "daName" parameter must specify the name of the desk
  152. * accessory (i.e. the name of the DRVR resource, including the initial null
  153. * character).  If the process is found, then information about the process is
  154. * returned in the "testFileInfo" parameter, and FindProcess returns TRUE.  If
  155. * the process could not be found, then FALSE is returned, and "testFileInfo" is
  156. * unchanged.
  157. *
  158. * Warning: the processName and processAppSpec fields of "testFileInfo" are NOT
  159. * valid when FindProcess returns TRUE.  If the name and FSSpec of the found file
  160. * are desired, then the calling routine must allocate space for those fields
  161. * and call GetProcessInfo itself, passing the ProcessInfoRec returned by this
  162. * routine as a parameter.
  163. *******************************************************************************)
  164.  
  165.     FUNCTION FindProcess (testFile:         FSSpec;
  166.                           daName:           StringPtr;
  167.                           VAR testFileInfo: ProcessInfoRec): Boolean;
  168.  
  169.  
  170. (*******************************************************************************
  171. * LaunchProcess - Launch the specified process
  172. *
  173. * This routine launches the process whose file has the location and name
  174. * specified by "processFile" parameter.  The process’s resulting serial number
  175. * is returned in the returnPSN parameter.  If the process to be launched is a
  176. * desk accessory, then the name of the desk accessory (including the initial
  177. * null character that desk accessories require) is passed in daName.  IF NIL is
  178. * passed in daName, then the first desk accessory found in the specified file
  179. * (according to the Resource Manager) is launched.  If an application is being
  180. * launched, then daName is ignored.  The options parameter specifies options to
  181. * use when launching the new process.  It has the same values and meanings as
  182. * the LaunchFlags type defined in the Process Manager chapter of Inside
  183. * Macintosh VI.  If a desk accessory is being launched, then only the
  184. * launchContinue flag has significance.
  185. *
  186. * A list of documents to be opened or printed by the launched application can
  187. * optionally be specified by the docList parameter.  If no documents are
  188. * specified, then docList should be NIL.  See the document list routines defined
  189. * early in this unit.
  190. *
  191. * LaunchProcess determines whether to launch an application or desk accessory
  192. * based on the type code of the file specified by processFile.  If the file’s
  193. * type is APPL, then LaunchProcess attempts to launch it as an application.  If
  194. * the file has any other type, then LaunchProcess attempts to launch a desk
  195. * accessory in that file.
  196. *
  197. * LaunchProcess returns two kinds of errors.  One error is returned as a
  198. * function result.  These kinds of errors are generated by any call that occurs
  199. * during the execution of LaunchProcess that is only used to manage the
  200. * launching of the specified application rather than launching the application
  201. * itself.   The launchError parameter returns the error code of any error that
  202. * occurs when the application is actually launched.
  203. *******************************************************************************)
  204.  
  205.     FUNCTION LaunchProcess (processFile:     FSSpec;
  206.                             daName:          StringPtr;
  207.                             docList:         DocListHnd;
  208.                             options:         LaunchFlags;
  209.                             VAR returnPSN:   ProcessSerialNumber;
  210.                             VAR launchError: OSErr): OSErr;
  211.  
  212.  
  213. (*******************************************************************************
  214. * CountProcesses - Count the number of open processes
  215. *
  216. * This routine searches through the Process Manager’s process list and counts
  217. * the number of open processes.  The result is returned.
  218. *******************************************************************************)
  219.  
  220.     FUNCTION CountProcesses: Integer;
  221.  
  222.  
  223. (*******************************************************************************
  224. * TerminateProcess - Terminate a process
  225. *
  226. * This routine causes the process specified by "theProcessNum" to be terminated.
  227. * If an error occurs, then the error code is returned.
  228. *******************************************************************************)
  229.  
  230.     FUNCTION TerminateProcess (theProcessNum: ProcessSerialNumber): OSErr;
  231.  
  232.  
  233. IMPLEMENTATION
  234.  
  235.     USES
  236.         AppleEvents
  237.         ,Aliases
  238.         ,Resources
  239.         ,TextUtils;
  240.  
  241.     CONST
  242.         kCaseSens = TRUE; {Pass to EqualString for case-sensitive check}
  243.         kDiacSens = TRUE; {Pass to EqualString for diacritical-sensitive check}
  244.  
  245.  
  246. {$S ProcessUtils}
  247. (*******************************************************************************
  248. * Private: EqualFSSpec - Check equality of FSSpec records
  249. *
  250. * EqualFSSpec returns TRUE if the file specified by spec1 refers to the same
  251. * file as the one specified by spec2.  Otherwise, EqualFSSpec returns FALSE.
  252. *
  253. * To compare names, I’m using EqualString with no case sensitivity, but with
  254. * sensitivity to diacriticals.  This isn’t usually the recommended way of
  255. * comparing strings, because the Script Manager has routines for comparing
  256. * strings in a more sophisticated, localizable way.  But the File Manager uses
  257. * _CmpString, which is the assembly language equivalent of EqualString, so
  258. * that’s the way I must do things here.
  259. *******************************************************************************)
  260.  
  261.     FUNCTION EqualFSSpec (spec1: FSSpec;
  262.                           spec2: FSSpec): Boolean;
  263.  
  264.     BEGIN
  265.         EqualFSSpec := (spec1.vRefNum = spec2.vRefNum) AND
  266.                        (spec1.parID = spec2.parID) AND
  267.                        (EqualString (spec1.name, spec2.name, NOT kCaseSens,
  268.                         kDiacSens))
  269.     END;
  270.  
  271.  
  272. {$S ProcessUtils}
  273. (*******************************************************************************
  274. * Public: CreateDocList
  275. *
  276. * A document list record is allocated and initialized.  Pretty simple.
  277. *******************************************************************************)
  278.  
  279.     FUNCTION CreateDocList (openOrPrint: LaunchModeCode): DocListHnd;
  280.  
  281.         VAR
  282.             newList: DocListHnd; {Handle to the new DocListRec}
  283.  
  284.     BEGIN
  285.         IF (openOrPrint = kOpenLaunch) OR (openOrPrint = kPrintLaunch) THEN
  286.             BEGIN
  287.                 newList := DocListHnd(NewHandle (SIZEOF (DocListRec)));
  288.                 IF newList <> NIL THEN
  289.                     BEGIN
  290.                         newList^^.docList := NIL;
  291.                         newList^^.openPrint := openOrPrint
  292.                     END;
  293.                 CreateDocList := newList
  294.             END
  295.     END;
  296.  
  297.  
  298. {$S ProcessUtils}
  299. (*******************************************************************************
  300. * Public: IsEmptyDocList
  301. *
  302. * If the document list record is NIL, or if the docList field is NIL, then the
  303. * given document list is empty.
  304. *******************************************************************************)
  305.  
  306.     FUNCTION IsEmptyDocList (theList: DocListHnd): Boolean;
  307.  
  308.     BEGIN
  309.         IsEmptyDocList := (theList = NIL) | (theList^^.docList = NIL)
  310.     END;
  311.  
  312.  
  313. {$S ProcessUtils}
  314. (*******************************************************************************
  315. * Public: AddToDocList
  316. *
  317. * A new document list entry is allocated and then "newFile" is copied into its
  318. * docFile field.  It is then added to end of "theList".
  319. *******************************************************************************)
  320.  
  321.     FUNCTION AddToDocList (newFile: FSSpec;
  322.                            theList: DocListHnd): OSErr;
  323.  
  324.         VAR
  325.             newEntry:  DocListEntryHnd; {Handle to the new DocListEntryRec}
  326.             currEntry: DocListEntryHnd; {Handle to DocListEntryRec being checked}
  327.  
  328.     BEGIN
  329.         (* Create a new document list entry *)
  330.         newEntry := DocListEntryHnd (NewHandle (SIZEOF (DocListEntryRec)));
  331.         IF newEntry <> NIL THEN
  332.             BEGIN
  333.                 (* Initialize the new document list entry *)
  334.                 newEntry^^.next := NIL;
  335.                 newEntry^^.docFile := newFile;
  336.  
  337.                 IF IsEmptyDocList (theList) THEN
  338.                     theList^^.docList := newEntry
  339.                 ELSE
  340.                     BEGIN
  341.                         (* Start from the head of the list *)
  342.                         currEntry := theList^^.docList;
  343.  
  344.                         (* Find the end of the list *)
  345.                         WHILE currEntry^^.next <> NIL DO
  346.                             currEntry := currEntry^^.next;
  347.  
  348.                         (* Attach the new entry to the end of the list *)
  349.                         currEntry^^.next := newEntry
  350.                     END;
  351.                 AddToDocList := noErr
  352.             END
  353.         ELSE
  354.             AddToDocList := MemError
  355.     END;
  356.  
  357.  
  358. {$S ProcessUtils}
  359. (*******************************************************************************
  360. * Private: DisposeAllDocEntries - Dispose of all document list entries
  361. *
  362. * Ahhh. . . good old, basic, text-book recursion.  This routine has a very small
  363. * stack frame, so the recursion isn’t very expensive in this case.  Document
  364. * lists are normally very short too.
  365. *
  366. * This routine disposes of every document list entry in a linked list of
  367. * document list entries starting from the entry docListEntry.  If docListEntry
  368. * isn’t NIL and it isn’t a valid DocListEntryHnd, DisposeAllDocEntries probably
  369. * blows up in very bad ways.
  370. *******************************************************************************)
  371.  
  372.     PROCEDURE DisposeAllDocEntries (docListEntry: DocListEntryHnd);
  373.  
  374.     BEGIN
  375.         IF docListEntry <> NIL THEN
  376.             BEGIN
  377.                 IF docListEntry^^.next <> NIL THEN
  378.                     DisposeAllDocEntries (docListEntry^^.next);
  379.                 DisposeHandle (Handle(docListEntry))
  380.             END
  381.     END;
  382.  
  383.  
  384. {$S ProcessUtils}
  385. (*******************************************************************************
  386. * Public: DisposeDocList
  387. *
  388. * Should be pretty obvious how this works.
  389. *******************************************************************************)
  390.  
  391.     PROCEDURE DisposeDocList (theList: DocListHnd);
  392.  
  393.     BEGIN
  394.         IF theList <> NIL THEN
  395.             BEGIN
  396.                 (* Dispose of all document list entries *)
  397.                 DisposeAllDocEntries (theList^^.docList);
  398.  
  399.                 (* Dispose of the document list record *)
  400.                 DisposeHandle (Handle(theList))
  401.             END
  402.     END;
  403.  
  404.  
  405. {$S ProcessUtils}
  406. (*******************************************************************************
  407. * Public: WereInFront
  408. *
  409. * The current application’s process serial number can always be represented by
  410. * setting the high long integer to 0 and setting the low long integer to the
  411. * constant kCurrentProcess.  This process serial number is compared against the
  412. * process serial number of the front application.  If they’re the same, then
  413. * this application is the front application.
  414. *******************************************************************************)
  415.  
  416.     FUNCTION WereInFront: Boolean;
  417.  
  418.         VAR
  419.             thisProcess:  ProcessSerialNumber; {Current process’s PSN}
  420.             frontProcess: ProcessSerialNumber; {Front process’s PSN}
  421.             inFront:      Boolean;             {TRUE if we’re in front}
  422.             error:        OSErr;
  423.  
  424.         PROCEDURE RecoverError (errorCode: OSErr);
  425.  
  426.         BEGIN
  427.             {$unused errorCode}
  428.             WereInFront := TRUE;
  429.             EXIT (WereInFront)
  430.         END;
  431.  
  432.     BEGIN
  433.         (* Make a PSN for this application *)
  434.         thisProcess.highLongOfPSN := 0;
  435.         thisProcess.lowLongOfPSN := kCurrentProcess;
  436.  
  437.         (* Get the PSN of the front process *)
  438.         error := GetFrontProcess ((*<*)frontProcess);
  439.         IF error <> noErr THEN
  440.             RecoverError (error);
  441.  
  442.         (* See if this application is the front process *)
  443.         error := SameProcess (thisProcess, frontProcess, (*<*)inFront);
  444.         IF error <> noErr THEN
  445.             RecoverError (error);
  446.  
  447.         WereInFront := inFront
  448.     END;
  449.  
  450.  
  451. {$S ProcessUtils}
  452. (*******************************************************************************
  453. * Private: CreateDocListDesc - Create an AE descriptor for a list of documents
  454. *
  455. * This routine takes a document list in the "docList" parameter and converts it
  456. * to an 'appa' (typeAppParameters) AppleEvent descriptor containing a list of
  457. * documents to be opened by the Process Manager’s LaunchApplication routine.
  458. * This descriptor is returned.
  459. *
  460. * To do this, we have to first build an AppleEvent for an 'odoc'
  461. * (kAEOpenDocuments) or a 'pdoc' (kAEPrintDocuments) event that contains the
  462. * same list of documents that are contained in the "theDocList" parameter.  Once
  463. * that’s done, this AppleEvent is coerced into an typeAppParameters descriptor.
  464. *
  465. * To create the kAEOpenDocuments or kAEPrintDocuments AppleEvent, a target
  466. * address descriptor is needed.  Because we’re converting the AppleEvent into a
  467. * typeAppParameters descriptor rather than sending it somewhere, it doesn’t
  468. * matter what target address we use.  I just used the process serial number
  469. * (PSN) of this application as a dummy value.  Because this is an application
  470. * and not something like a driver, I can just use the "kCurrentProcess" constant
  471. * to represent this application’s PSN.  A descriptor is made of this PSN, then
  472. * this PSN is used when creating the new kAEOpenDocuments AppleEvent.  Once this
  473. * AppleEvent is created, the PSN descriptor is no longer needed and is disposed
  474. * of.
  475. *
  476. * kAEOpenDocuments AppleEvents needs a list of file aliases; each file alias
  477. * represents a document to open.  To do this, a new descriptor list is created,
  478. * and an alias record is created.  Then, each document in the "theDocList"
  479. * parameter is converted to an alias in the alias record, then the alias record
  480. * is manually converted into an 'alis' (typeAlias) descriptor.  I did this
  481. * manually because an alias is already a handle, and it didn’t seem necessary to
  482. * go through the extra expense of the AECreateDesc routine.  This typeAlias
  483. * descriptor is then added to the descriptor list we created earlier in this
  484. * paragraph.  Once the entire document list is converted to the descriptor list,
  485. * the alias record is no longer needed and is disposed of.
  486. *
  487. * This descriptor list full of document aliases is then added to the
  488. * kAEOpenDocuments AppleEvent I created.  Because the descriptor list is copied
  489. * into the AppleEvent, it is disposed of after the AEPutParamDesc call.
  490. *
  491. * Then, the most crucial call is made, AECoerceDesc.  This routine coerces the
  492. * AppleEvent we just made into a typeAppParameters descriptor.  The record
  493. * specified by the dataHandle of this descriptor is exactly in the same format
  494. * as the AppParameters record defined in the “Launching Other Applications”
  495. * section of the Process Manager chapter of Inside Macintosh VI.
  496. *
  497. * You can HLock the dataHandle, then set the "launchAppParameters" field of the
  498. * LaunchParamBlockRec to the dereferenced dataHandle.  LaunchApplication can
  499. * then launch the specified application with the document list that was created.
  500. * If the launched application is high-level event aware (as specified in its
  501. * SIZE resource), the Process Manager converts LaunchAppParameters back to a
  502. * real high-level event which is processed by the application to (hopefully)
  503. * open the documents.  Remember that LaunchAppParameters can be any high-level
  504. * event, but it’ll usually be used to pass an 'odoc' or 'pdoc' event, and that’s
  505. * what we’re using here.  If the launched application isn’t high-level event
  506. * aware, and if LaunchAppParameters is an 'odoc' or 'pdoc' event, then the
  507. * Process Manager will convert the 'odoc' or 'pdoc' event into the old-style
  508. * application parameters so that an old-style application can still open
  509. * documents properly.
  510. *
  511. * This routine is important.  Read it.  Understand it.  Then teach me how it
  512. * works.
  513. *******************************************************************************)
  514.  
  515.     FUNCTION CreateDocListDesc (theDocList:     DocListHnd;
  516.                                 VAR launchDesc: AEDesc): OSErr;
  517.  
  518.         CONST
  519.             kPutAtEnd = 0; {For AEPutDesc; put new descriptor at end of list}
  520.  
  521.         VAR
  522.             applMessage:  AppleEvent;          {odoc/pdoc event w/ spec’d doc list}
  523.             docDescList:  AEDescList;          {Desc list of doc alias descs}
  524.             selfPSN:      ProcessSerialNumber; {Our own PSN; not really used here}
  525.             selfAddress:  AEDesc;              {Desc for our own PSN; ditto}
  526.             docDesc:      AEDesc;              {Desc for doc alias}
  527.             docAlias:     AliasHandle;         {Alias for specified docs}
  528.             currDocEntry: DocListEntryHnd;     {Handle to current doc list entry}
  529.             openPrintCmd: AEEventID;           {odoc or pdoc event?}
  530.             wasChanged:   Boolean;             {TRUE if UpdateAlias changed alias}
  531.             error:        OSErr;
  532.  
  533.         PROCEDURE RecoverError (errorCode: Integer);
  534.  
  535.         BEGIN
  536.             IF selfAddress.dataHandle <> NIL THEN
  537.                 error := AEDisposeDesc ((*◊*)selfAddress);
  538.             IF applMessage.dataHandle <> NIL THEN
  539.                 error := AEDisposeDesc ((*◊*)applMessage);
  540.             IF docDescList.dataHandle <> NIL THEN
  541.                 error := AEDisposeDesc ((*◊*)docDescList);
  542.             IF docAlias <> NIL THEN
  543.                 DisposeHandle (Handle(docAlias));
  544.             CreateDocListDesc := errorCode;
  545.             EXIT (CreateDocListDesc)
  546.         END;
  547.  
  548.     BEGIN
  549.         selfAddress.dataHandle := NIL;
  550.         applMessage.dataHandle := NIL;
  551.         docDescList.dataHandle := NIL;
  552.         docAlias := NIL;
  553.  
  554.         (* Make descriptor for my own PSN; just for AECreateAppleEvent’s yuks *)
  555.         selfPSN.highLongOfPSN := 0;
  556.         selfPSN.lowLongOfPSN := kCurrentProcess;
  557.         error := AECreateDesc (typeProcessSerialNumber, @selfPSN,
  558.                 SIZEOF (ProcessSerialNumber), (*<*)selfAddress);
  559.         IF error <> noErr THEN
  560.             RecoverError (error);
  561.  
  562.         (* Create an AppleEvent for our list of document descriptors *)
  563.         IF theDocList^^.openPrint = kOpenLaunch THEN
  564.             openPrintCmd := kAEOpenDocuments
  565.         ELSE IF theDocList^^.openPrint = kPrintLaunch THEN
  566.             openPrintCmd := kAEPrintDocuments;
  567.         error := AECreateAppleEvent (kCoreEventClass, openPrintCmd, selfAddress,
  568.                 kAutoGenerateReturnID, kAnyTransactionID, (*<*)applMessage);
  569.         IF error <> noErr THEN
  570.             RecoverError (error);
  571.  
  572.         (* PSN copied into the odoc or pdoc event, so don’t need it anymore *)
  573.         error := AEDisposeDesc ((*◊*)selfAddress);
  574.  
  575.         (* Create list of descriptors for files; don’t use list factorization *)
  576.         error := AECreateList (NIL, 0, FALSE, (*<*)docDescList);
  577.         IF error <> noErr THEN
  578.             RecoverError (error);
  579.  
  580.         (* Create an alias for the first document *)
  581.         currDocEntry := theDocList^^.docList;
  582.         HLock (Handle(currDocEntry));
  583.         error := NewAlias (NIL, currDocEntry^^.docFile, (*<*)docAlias);
  584.         HUnlock (Handle(currDocEntry));
  585.         IF error <> noErr THEN
  586.             RecoverError (error);
  587.  
  588.         (* Put each document in the document list into document descriptor list *)
  589.         WHILE currDocEntry <> NIL DO
  590.             BEGIN
  591.                 (* Convert alias into an alias descriptor manually *)
  592.                 docDesc.descriptorType := typeAlias;
  593.                 docDesc.dataHandle := Handle(docAlias);
  594.  
  595.                 (* Put the alias descriptor into the document descriptor list *)
  596.                 error := AEPutDesc ((*◊*)docDescList, kPutAtEnd, docDesc);
  597.                 IF error <> noErr THEN
  598.                     RecoverError (error);
  599.  
  600.                 (* Go to the next document in the document list *)
  601.                 currDocEntry := currDocEntry^^.next;
  602.  
  603.                 (* Convert the next document’s FSSpec into the alias *)
  604.                 IF currDocEntry <> NIL THEN
  605.                     BEGIN
  606.                         HLock (Handle(currDocEntry));
  607.                         error := UpdateAlias  (NIL, currDocEntry^^.docFile,
  608.                                 (*<*)docAlias, (*<*)wasChanged);
  609.                         HUnlock (Handle(currDocEntry));
  610.                         IF error <> noErr THEN
  611.                             RecoverError (error)
  612.                     END
  613.             END;
  614.  
  615.         (* All aliases are in docDescList, so don’t need the alias record *)
  616.         DisposeHandle (Handle(docAlias));
  617.         docAlias := NIL;
  618.  
  619.         (* Put the descriptor list of documents into the odoc or pdoc event *)
  620.         error := AEPutParamDesc ((*◊*)applMessage, keyDirectObject, docDescList);
  621.         IF error <> noErr THEN
  622.             RecoverError (error);
  623.  
  624.         (* The descriptor list is copied into the odoc event, so get rid of it *)
  625.         error := AEDisposeDesc (docDescList);
  626.  
  627.         (* Convert the odoc event to a descriptor suitable for the launch PB *)
  628.         error := AECoerceDesc (applMessage, typeAppParameters, (*<*)launchDesc);
  629.         IF error <> noErr THEN
  630.             RecoverError (error);
  631.  
  632.         (* We’re really interested in launchDesc, so don’t need odoc message *)
  633.         error := AEDisposeDesc (applMessage);
  634.  
  635.         CreateDocListDesc := noErr
  636.     END;
  637.  
  638.  
  639. {$S ProcessUtils}
  640. (*******************************************************************************
  641. * Private: SendOpenAppEvent - Send an 'oapp' event to an application
  642. *
  643. * When an application that is high-level event aware (as indicated by the SIZE
  644. * resource flag), it won’t open an untitled document at launch until it gets an
  645. * 'oapp' AppleEvent.  This routine sends an 'oapp' event to the application
  646. * specified by the processNum parameter.
  647. *
  648. * First, a descriptor is created that contains the process serial number of the
  649. * target application.  This descriptor is then added to the 'oapp' AppleEvent.
  650. * Finally, this AppleEvent is sent to the target application which should open
  651. * an untitled document in response.
  652. *
  653. * This routine isn’t called if the application is to open specific documents
  654. * when it’s launched.
  655. *
  656. * If an error occurs, the error code is returned.
  657. *******************************************************************************)
  658.  
  659.     FUNCTION SendOpenAppEvent (processNum: ProcessSerialNumber): OSErr;
  660.  
  661.         VAR
  662.             theBaby:   AEAddressDesc; {PSN desc. of process that’s been opened}
  663.             openEvent: AppleEvent;    {'oapp' AppleEvent}
  664.             reply:     AppleEvent;    {Reply from receiving application; ignored}
  665.             error:     OSErr;
  666.  
  667.         PROCEDURE RecoverError (errorCode: Integer);
  668.  
  669.         BEGIN
  670.             IF theBaby.dataHandle <> NIL THEN
  671.                 error := AEDisposeDesc ((*◊*)theBaby);
  672.             IF openEvent.dataHandle <> NIL THEN
  673.                 error := AEDisposeDesc ((*◊*)openEvent);
  674.             IF reply.dataHandle <> NIL THEN
  675.                 error := AEDisposeDesc ((*◊*)reply);
  676.             SendOpenAppEvent := errorCode;
  677.             EXIT (SendOpenAppEvent)
  678.         END;
  679.  
  680.     BEGIN
  681.         theBaby.dataHandle := NIL;
  682.         openEvent.dataHandle := NIL;
  683.         reply.dataHandle := NIL;
  684.  
  685.         (* Create the Process Serial Number event descriptor *)
  686.         error := AECreateDesc (typeProcessSerialNumber, Ptr(@processNum),
  687.                 SIZEOF (ProcessSerialNumber), (*<*)theBaby);
  688.         IF error <> noErr THEN
  689.             RecoverError (error);
  690.  
  691.         (* Create 'oapp' event with the specified process serial number *)
  692.         error := AECreateAppleEvent (kCoreEventClass, kAEOpenApplication,
  693.                 theBaby, kAutoGenerateReturnID, kAnyTransactionID, (*<*)openEvent);
  694.         IF error <> noErr THEN
  695.             RecoverError (error);
  696.  
  697.         (* Send the 'oapp' event *)
  698.         error := AESend (openEvent, (*<*)reply, kAENoReply, kAENormalPriority,
  699.                 0, NIL, NIL);
  700.         IF error <> noErr THEN
  701.             RecoverError (error);
  702.  
  703.         (* Dispose of the descriptor and event *)
  704.         error := AEDisposeDesc ((*◊*)theBaby);
  705.         error := AEDisposeDesc ((*◊*)openEvent);
  706.         IF reply.dataHandle <> NIL THEN
  707.             error := AEDisposeDesc ((*◊*)reply)
  708.     END;
  709.  
  710.  
  711. {$S ProcessUtils}
  712. (*******************************************************************************
  713. * Private: LaunchProcApp - Launch an application
  714. *
  715. * This routine saves a bit of work when launching an application because it sets
  716. * up the launch parameter block and manages the construction of the application
  717. * list descriptor.  The application to open is specified in processFile.  If
  718. * there are any documents for the application to open when it’s launched, then
  719. * the document list is passed in docList.  If there aren’t any documents for the
  720. * application to open, then docList is NIL.  The options parameter specifies the
  721. * options to use when launching.  This contains the values for the LaunchFlags
  722. * data type specified in the Process Manager chapter of Inside Macintosh VI.
  723. *
  724. * The process serial number of the specified process is returned in the
  725. * processNum parameter.  The function result and the launchError parameter both
  726. * return error codes.  One error is returned as a function result.  These kinds
  727. * of errors are generated by any call that occurs during the execution of
  728. * LaunchProcess that is only used to manage the launching of the specified
  729. * application rather than launching the application itself.   The launchError
  730. * parameter returns the error code of any error that occurs when the application
  731. * is actually launched.
  732. *******************************************************************************)
  733.  
  734.     FUNCTION LaunchProcApp (processFile:     FSSpec;
  735.                             docList:         DocListHnd;
  736.                                     options:         LaunchFlags;
  737.                             VAR processNum:  ProcessSerialNumber;
  738.                             VAR launchError: OSErr): OSErr;
  739.  
  740.         VAR
  741.             launchParms: LaunchParamBlockRec; {Parameters for launching a file}
  742.             launchDesc:  AEDesc;              {Document file list descriptor}
  743.             appParms:    AppParametersPtr;    {Pointer to the app parameters}
  744.             error:       OSErr;
  745.  
  746.         PROCEDURE RecoverError (errorCode: OSErr);
  747.  
  748.         BEGIN
  749.             IF launchDesc.dataHandle <> NIL THEN
  750.                 BEGIN
  751.                     HUnlock (Handle(launchDesc.dataHandle));
  752.                     error := AEDisposeDesc (launchDesc)
  753.                 END;
  754.             LaunchProcApp := errorCode;
  755.             EXIT (LaunchProcApp)
  756.         END;
  757.  
  758.     BEGIN
  759.         launchError := noErr;
  760.         launchDesc.dataHandle := NIL;
  761.  
  762.         (* Create the document list descriptor, if there’s a document list *)
  763.         IF docList <> NIL THEN
  764.             BEGIN
  765.                 error := CreateDocListDesc (docList, (*<*)launchDesc);
  766.                 IF error <> noErr THEN
  767.                     RecoverError (error);
  768.                 HLock (Handle(launchDesc.dataHandle));
  769.  
  770.                 (* Descriptor dataHandle in format suitable for LaunchApplication *)
  771.                 appParms := AppParametersPtr(launchDesc.dataHandle^)
  772.             END
  773.         ELSE
  774.             appParms := NIL;
  775.  
  776.         (* Set up the launch parameters *)
  777.         WITH launchParms DO
  778.             BEGIN
  779.                 (*WITH*)launchBlockID := extendedBlock;
  780.                 (*WITH*)launchEPBLength := extendedBlockLen;
  781.                 (*WITH*)launchFileFlags := 0;
  782.                 (*WITH*)launchControlFlags := options;
  783.                 (*WITH*)launchAppSpec := @processFile;
  784.                 (*WITH*)launchAppParameters := appParms
  785.             END;
  786.  
  787.         (* Launch the process *)
  788.         launchError := LaunchApplication (@launchParms);
  789.  
  790.         (* Get rid of the document descriptor list, if there was one *)
  791.         IF docList <> NIL THEN
  792.             BEGIN
  793.                 HUnlock (Handle(launchDesc.dataHandle));
  794.                 error := AEDisposeDesc (launchDesc)
  795.             END
  796.         ELSE
  797.             (* No document descriptor list, so send oapp AppleEvent *)
  798.             IF launchError = noErr THEN
  799.                 error := SendOpenAppEvent (launchParms.launchProcessSN);
  800.  
  801.         (* If the launch was successful, return the PSN of the process *)
  802.         IF launchError = noErr THEN
  803.             processNum := launchParms.launchProcessSN;
  804.  
  805.         LaunchProcApp := noErr
  806.     END;
  807.  
  808.  
  809. {$S ProcessUtils}
  810. (*******************************************************************************
  811. * Private: GetFirstDAName - Get name of first 
  812. *
  813. * GetFirstDAName gets the name of the first desk accessory in the file specified
  814. * by daFile.  This name is returned in the daName parameter.  GetFirstDAName
  815. * returns TRUE if a desk accessory was found in the file.  If no desk accessory
  816. * could be found, then GetFirstDAName returns FALSE and the daName parameter is
  817. * unchanged.
  818. *
  819. * Desk accessories are stored in DRVR resources, but device drivers are also
  820. * stored in DRVR resources.  GetFirstDAName will not return the names of device
  821. * drivers.  How do you tell whether a DRVR resource is a desk accessory or a
  822. * device driver?  Simply check on the first character of the DRVR resource’s
  823. * name.  If it’s a null character, then you’ve got a desk accessory.  If it’s
  824. * any other character, then you’ve got a device driver.
  825. *******************************************************************************)
  826.  
  827.     FUNCTION GetFirstDAName (daFile:     FSSpec;
  828.                              VAR daName: Str255): Boolean;
  829.  
  830.         VAR
  831.             currRF:   Integer; {Ref num of current resource file}
  832.             daRF:     Integer; {Ref num of DA’s resource file}
  833.             drvrRsrc: Handle;  {Handle to a DRVR resource}
  834.             drvrID:   Integer; {ID number of drvrRsrc}
  835.             drvrType: ResType; {Type of resource}
  836.             drvrName: Str255;  {Name of resource}
  837.             numDRVRs: Integer; {Number of DRVR resources in the file}
  838.             index:    Integer; {Index into DRVR resources}
  839.             daFound:  Boolean; {TRUE if a DA was found in daFile}
  840.  
  841.     BEGIN
  842.         daFound := FALSE;
  843.  
  844.         (* Save the current resource file ref num so we can restore it later *)
  845.         currRF := CurResFile;
  846.  
  847.         (* Open the specified resource file *)
  848.         daRF := FSpOpenResFile (daFile, fsRdPerm);
  849.         IF daRF <> -1 THEN
  850.             BEGIN
  851.                 (* Ready to check each DRVR; only checking, so set ResLoad false *)
  852.                 SetResLoad (FALSE);
  853.                 index := 1;
  854.                 numDRVRs := Count1Resources ('DRVR');
  855.  
  856.                 (* Check each DRVR until all checked or a DA was found *)
  857.                 WHILE (index <= numDRVRs) AND (NOT daFound) DO
  858.                     BEGIN
  859.                         drvrRsrc := Get1IndResource ('DRVR', index);
  860.  
  861.                         (* Get information about the resource *)
  862.                         IF drvrRsrc <> NIL THEN
  863.                             BEGIN
  864.                                 GetResInfo (drvrRsrc, (*<*)drvrID, (*<*)drvrType,
  865.                                         (*<*)drvrName);
  866.  
  867.                                 (* Find out whether it’s a DA or not *)
  868.                                 IF (drvrName [0] > CHR (0)) & (drvrName [1] = CHR (0))
  869.                                         THEN
  870.                                     daFound := TRUE
  871.                                 ELSE
  872.                                     index := SUCC (index)
  873.                             END
  874.                     END;
  875.  
  876.                 (* Restore the resource file and reset ResLoad *)
  877.                 SetResLoad (TRUE);
  878.                 CloseResFile (daRF)
  879.             END;
  880.  
  881.         (* Get name of first DA in the file, or nil string if none found *)
  882.         IF daFound THEN
  883.             daName := drvrName
  884.         ELSE
  885.             daName [0] := CHR (0);
  886.         GetFirstDAName := daFound;
  887.         UseResFile (currRF)
  888.     END;
  889.  
  890.  
  891. {$S ProcessUtils}
  892. (*******************************************************************************
  893. * Public: FindProcess
  894. *
  895. * The critical routine called by FindProcess is the GetProcessInfo routine.  The
  896. * process information it returns is checked against "testFile", and against
  897. * "daName" if a desk accessory is being checked.  If they’re equal, then
  898. * FindProcess returns TRUE and puts the process information in "testFileInfo".
  899. * If the entire Process Manager process list is searched without finding a
  900. * match, then FALSE is returned.
  901. *******************************************************************************)
  902.  
  903.     FUNCTION FindProcess (testFile:         FSSpec;
  904.                           daName:           StringPtr;
  905.                           VAR testFileInfo: ProcessInfoRec): Boolean;
  906.  
  907.         VAR
  908.             procInfo:  ProcessInfoRec;      {Information on process being checked}
  909.             procNum:   ProcessSerialNumber; {Serial num of process being checked}
  910.             procName:  Str255;              {Name of open process}
  911.             procSpec:  FSSpec;              {File spec of open process’s file}
  912.             procFound: Boolean;             {TRUE if matching DA was found}
  913.             error:     OSErr;
  914.  
  915.     BEGIN
  916.         (* Start checking from first process in Process Manager’s list *)
  917.         procNum.highLongOfPSN := 0;
  918.         procNum.lowLongOfPSN := kNoProcess;
  919.  
  920.         (* Loop through entire list of open processes or until match *)
  921.         procFound := FALSE;
  922.         WHILE (NOT procFound) & (GetNextProcess ((*◊*)procNum) = noErr) DO
  923.             BEGIN
  924.                 (* Get information about an open process *)
  925.                 procInfo.processInfoLength := SIZEOF (ProcessInfoRec);
  926.                 procInfo.processName := @procName;
  927.                 procInfo.processAppSpec := @procSpec;
  928.                 error := GetProcessInformation (procNum, (*◊*)procInfo);
  929.  
  930.                 (* Is it the same file as the one we’re testing? *)
  931.                 IF error <> noErr THEN
  932.                     IF EqualFSSpec (procInfo.processAppSpec^, testFile) THEN
  933.                         (* Yes; if it’s an application, we’ve found matching app *)
  934.                         IF BAND (procInfo.processMode, modeDeskAccessory) = 0 THEN
  935.                             procFound := TRUE
  936.                         ELSE
  937.                             (* Is it the same DA as the one we’re testing? *)
  938.                             IF EqualString (procInfo.processName^, daName^,
  939.                                     NOT kCaseSens, kDiacSens) THEN
  940.                                 (* Yes, we’ve found the matching DA *)
  941.                                 procFound := TRUE
  942.             END;
  943.  
  944.         IF procFound THEN
  945.             testFileInfo := procInfo;
  946.         FindProcess := procFound
  947.     END;
  948.  
  949.  
  950. {$S ProcessUtils}
  951. (*******************************************************************************
  952. * Private: LaunchProcDA - Launch a desk accessory
  953. *
  954. * This routine launches the desk accessory that’s in the file specified by
  955. * process file and with the name specified by daName.  If daName is NIL, then
  956. * the first desk accessory in the file, according to Get1IndResource, is
  957. * launched.
  958. *
  959. * As of 7.0b1, LaunchDeskAccessory doesn’t quite work as advertised in Inside
  960. * Macintosh VI if the desk accessory that it’s launching is already open.
  961. * Inside Macintosh VI says that the open desk accessory that’s being launched is
  962. * simply brought to the front.  What I’ve found is that LaunchDeskAccessory
  963. * doesn’t do anything but return opWrErr.  If the file that the desk accessory
  964. * is in is locked, then a new copy of the desk accessory is opened.  I didn’t
  965. * quite like either of those actions, so I explicitly check to see whether the
  966. * desk accessory that I’m about to launch is already open.  If it is, then I
  967. * call SetFrontProcess myself to bring it to the front.
  968. *
  969. * To check for this case, I first make sure that I have a name for the desk
  970. * accessory.  LaunchDeskAccessory accepts NIL as a desk accessory name if the
  971. * caller wants to launch the first desk accessory that the Process Manager finds
  972. * in the specified file.  My routine, LaunchProcess, also allows this.  But I
  973. * don’t know what the Process Manager thinks is the first desk accessory in the
  974. * file.  Since I’m comparing the characteristics of the desk accessory I’m about
  975. * to open against the list of open processes, and since I don’t know which desk
  976. * accessory is about to be launched if I leave the choice up to the Process
  977. * Manager, I just go and get the first desk accessory in the specified file
  978. * myself by calling my routine, GetFirstDAName.  Now that I’m guaranteed to have
  979. * a name for the desired desk accessory, I don’t have to second-guess the
  980. * Process Manager’s choice for the “first” desk accessory in the specified file.
  981. *
  982. * You can’t tell LaunchDeskAccessory that you want to terminate after the desk
  983. * accessory is launched, so I terminate myself by calling ExitToShell if the
  984. * terminate parameter was set to TRUE.  You also can’t tell LaunchDeskAccessory
  985. * that you want the desk accessory launched into the background.  I still
  986. * haven’t thought of a clean way to do this, so for now, I don’t offer that as
  987. * an option.  I can think of a few dirty ways.
  988. *******************************************************************************)
  989.  
  990.     FUNCTION LaunchProcDA (processFile:     FSSpec;
  991.                            daName:          StringPtr;
  992.                            options:         LaunchFlags;
  993.                            VAR processNum:  ProcessSerialNumber;
  994.                            VAR launchError: OSErr): OSErr;
  995.  
  996.         VAR
  997.             procInfo:    ProcessInfoRec;      {Used to search open processes}
  998.             firstDAName: Str255;              {File’s 1st DA name if none spec’d}
  999.             daOpen:      Boolean;             {TRUE if spec’d DA is already open}
  1000.             error:       OSErr;
  1001.  
  1002.     BEGIN
  1003.         error := noErr;
  1004.         launchError := noErr;
  1005.  
  1006.         (* If no DA name specified, get name of first DA in processFile *)
  1007.         IF daName = NIL THEN
  1008.             IF GetFirstDAName (processFile, (*<*)firstDAName) THEN
  1009.                 IF firstDAName [0] > CHR (0) THEN
  1010.                     daName := @firstDAName
  1011.                 ELSE
  1012.                     daName := NIL;
  1013.  
  1014.         (* If got name for new DA, see if DA process open w/ same name and file *)
  1015.         IF daName <> NIL THEN
  1016.             daOpen := FindProcess (processFile, daName, (*<*)procInfo)
  1017.         ELSE
  1018.             daOpen := FALSE;
  1019.  
  1020.         (* Launch the DA to the front, or set it to front if already launched *)
  1021.         IF daOpen THEN
  1022.             error := SetFrontProcess (procInfo.processNumber)
  1023.         ELSE
  1024.             launchError := LaunchDeskAccessory (processFile, daName^);
  1025.  
  1026.         (* If the terminate flag is set, glad to oblige *)
  1027.         IF BAND (options, launchContinue) = 0 THEN
  1028.             ExitToShell;
  1029.  
  1030.         (* Return the process serial number of the DA *)
  1031.         IF (error = noErr) AND (launchError = noErr) THEN
  1032.             BEGIN
  1033.                 daOpen := FindProcess (processFile, daName, (*<*)procInfo);
  1034.                 IF daOpen THEN
  1035.                     processNum := procInfo.processNumber
  1036.                 ELSE
  1037.                     BEGIN
  1038.                         procInfo.processNumber.highLongOfPSN := 0;
  1039.                         procInfo.processNumber.lowLongOfPSN := kNoProcess;
  1040.                         processNum := procInfo.processNumber
  1041.                     END
  1042.             END;
  1043.  
  1044.         LaunchProcDA := error
  1045.     END;
  1046.  
  1047.  
  1048. {$S ProcessUtils}
  1049. (*******************************************************************************
  1050. * Public: LaunchProcess
  1051. *
  1052. * The terminate and foreground parmeters are used to set up the flags for
  1053. * launching in the launchOptions variable.  The launch parameter block is set up
  1054. * with the specified file and launch flags.
  1055. *
  1056. * If the launchContinue flags is clear, then the LaunchApplication function
  1057. * doesn’t return even if it fails.
  1058. *******************************************************************************)
  1059.  
  1060.     FUNCTION LaunchProcess (processFile:     FSSpec;
  1061.                             daName:          StringPtr;
  1062.                             docList:         DocListHnd;
  1063.                                     options:         LaunchFlags;
  1064.                             VAR returnPSN:   ProcessSerialNumber;
  1065.                                     VAR launchError: OSErr): OSErr;
  1066.  
  1067.         VAR
  1068.             fileInfo: FInfo;               {File information for file to launch}
  1069.             newPSN:   ProcessSerialNumber; {PSN of launched application}
  1070.             error:    OSErr;
  1071.  
  1072.         PROCEDURE RecoverError (errorCode: OSErr);
  1073.  
  1074.         BEGIN
  1075.             LaunchProcess := errorCode;
  1076.             EXIT (LaunchProcess)
  1077.         END;
  1078.  
  1079.     BEGIN
  1080.         launchError := noErr;
  1081.  
  1082.         (* Find out whether file to launch is APPL or something else *)
  1083.         error := FSpGetFInfo (processFile, (*<*)fileInfo);
  1084.         IF error <> noErr THEN
  1085.             RecoverError (error);
  1086.  
  1087.         (* Launch the process *)
  1088.         IF fileInfo.fdType = 'APPL' THEN
  1089.             BEGIN
  1090.                 (* File to be launched is an application; launch it *)
  1091.                 error := LaunchProcApp (processFile, docList, options, (*<*)newPSN,
  1092.                         (*<*)launchError);
  1093.                 IF error <> noErr THEN
  1094.                     RecoverError (error);
  1095.             END
  1096.         ELSE
  1097.             BEGIN
  1098.                 (* File to be launch is a desk accessory; launch it *)
  1099.                 error := LaunchProcDA (processFile, daName, options, (*<*)newPSN,
  1100.                         (*<*)launchError);
  1101.                 IF error <> noErr THEN
  1102.                     RecoverError (error)
  1103.             END;
  1104.  
  1105.         (* Return the PSN of the new process *)
  1106.         IF launchError = noErr THEN
  1107.             returnPSN := newPSN;
  1108.  
  1109.         LaunchProcess := noErr
  1110.     END;
  1111.  
  1112.  
  1113. {$S ProcessUtils}
  1114. (*******************************************************************************
  1115. * Public: CountProcesses
  1116. *
  1117. * It’s a pretty simple and straightforward algorithm.
  1118. *******************************************************************************)
  1119.  
  1120.     FUNCTION CountProcesses: Integer;
  1121.  
  1122.         VAR
  1123.             procNum:   ProcessSerialNumber; {Serial num of process being checked}
  1124.             procCount: Integer;             {Number of processes found}
  1125.  
  1126.     BEGIN
  1127.         (* Start checking from first process in Process Manager’s list *)
  1128.         procNum.highLongOfPSN := 0;
  1129.         procNum.lowLongOfPSN := kNoProcess;
  1130.  
  1131.         (* Loop through entire list of open processes *)
  1132.         procCount := 0;
  1133.         WHILE GetNextProcess ((*◊*)procNum) = noErr DO
  1134.             procCount := SUCC (procCount);
  1135.  
  1136.         (* Return the number of processes found *)
  1137.         CountProcesses := procCount
  1138.     END;
  1139.  
  1140.  
  1141. {$S ProcessUtils}
  1142. (*******************************************************************************
  1143. * Public: TerminateProcess
  1144. *
  1145. * Terminating a process is done by sending a 'quit' AppleEvent to the process
  1146. * whose process serial number is given by "theProcessNum".
  1147. *******************************************************************************)
  1148.  
  1149.     FUNCTION TerminateProcess (theProcessNum: ProcessSerialNumber): OSErr;
  1150.  
  1151.         VAR
  1152.             theDoomed: AEAddressDesc; {PSN descriptor of process to be terminated}
  1153.             quitEvent: AppleEvent;    {'quit' AppleEvent}
  1154.             reply:     AppleEvent;    {Reply from receiving application; ignored}
  1155.             error:     OSErr;
  1156.  
  1157.         PROCEDURE RecoverError (error: Integer);
  1158.  
  1159.             VAR
  1160.                 result: OSErr;
  1161.  
  1162.         BEGIN
  1163.             IF theDoomed.dataHandle <> NIL THEN
  1164.                 result := AEDisposeDesc ((*◊*)theDoomed);
  1165.             IF quitEvent.dataHandle <> NIL THEN
  1166.                 result := AEDisposeDesc ((*◊*)quitEvent);
  1167.             TerminateProcess := error;
  1168.             EXIT (TerminateProcess)
  1169.         END;
  1170.  
  1171.     BEGIN
  1172.         theDoomed.dataHandle := NIL;
  1173.         quitEvent.dataHandle := NIL;
  1174.         reply.dataHandle := NIL;
  1175.  
  1176.         (* Create the Process Serial Number event descriptor *)
  1177.         error := AECreateDesc (typeProcessSerialNumber, Ptr(@theProcessNum),
  1178.                 SIZEOF (theProcessNum), (*<*)theDoomed);
  1179.         IF error <> noErr THEN
  1180.             RecoverError (error);
  1181.  
  1182.         (* Create 'quit' event with the specified process serial number *)
  1183.         error := AECreateAppleEvent (kCoreEventClass, kAEQuitApplication,
  1184.                 theDoomed, kAutoGenerateReturnID, kAnyTransactionID, (*<*)quitEvent);
  1185.         IF error <> noErr THEN
  1186.             RecoverError (error);
  1187.  
  1188.         (* Send the 'quit' event *)
  1189.         error := AESend (quitEvent, (*<*)reply, kAENoReply,
  1190.                 kAENormalPriority, kNoTimeOut, NIL, NIL);
  1191.         IF error <> noErr THEN
  1192.             RecoverError (error);
  1193.  
  1194.         (* PSN in the AppleEvent, so can dispose of PSN descriptor *)
  1195.         error := AEDisposeDesc ((*◊*)theDoomed);
  1196.  
  1197.         (* Dispose of the 'quit' AppleEvent *)
  1198.         error := AEDisposeDesc ((*◊*)quitEvent);
  1199.         
  1200.         TerminateProcess := noErr;
  1201.     END;
  1202.  
  1203. END.
  1204.